/**
 * @author mrdoob / http://mrdoob.com/
 */

import * as THREE from '../../js/threejs/build/three.module.js'

import {History as _History} from './History.js'
import {Config} from '../../js/threejs/Config.js'
import {Loader} from '../../js/threejs/Loader.js'
import {LoaderRemote} from '../../js/threejs/Loader.Remote.js'
import {Strings} from '../../js/threejs/Strings.js'
import {Storage as _Storage} from '../../js/threejs/Storage.js'
import {StorageRemote} from '../../js/threejs/Storage.Remote.js'

import {Request} from '../../js/threejs/Request.js'

var Editor = function () {
  this.DEFAULT_CAMERA = new THREE.PerspectiveCamera(50, 1, 0.01, 1000)
  this.DEFAULT_CAMERA.name = 'Camera'
  this.DEFAULT_CAMERA.position.set(0, 5, 10)
  this.DEFAULT_CAMERA.lookAt(new THREE.Vector3())

  this.USERNAME = this.getQueryVariable('username') // 权限认证token
  this.MODE = this.getQueryVariable('mode') // 显示模式：deploy：部署；display：展示
  this.TOKEN = this.getQueryVariable('token') // 权限认证token
  this.SCENE_ID = this.getQueryVariable('sceneId')
  this.BASE_URL = this.getQueryVariable('baseUrl') // 后台项目跟路径
  this.DEPLOY_CONFIG = {} // 场景及设备的部署配置信息

  this.request = new Request(this.BASE_URL, this.TOKEN)
  // this.notification = new Notification(this);

  // this.notification.message(`通知`, `欢迎使用3D物联网平台`)

  var Signal = signals.Signal

  this.signals = {
    // script

    editScript: new Signal(),

    savingStarted: new Signal(),
    savingFinished: new Signal(),

    transformModeChanged: new Signal(),
    snapChanged: new Signal(),
    spaceChanged: new Signal(),
    rendererChanged: new Signal(),
    rendererUpdated: new Signal(),

    sceneBackgroundChanged: new Signal(),
    sceneFogChanged: new Signal(),
    sceneGraphChanged: new Signal(),
    sceneRendered: new Signal(),

    cameraAdded: new Signal(),
    cameraRemoved: new Signal(),
    cameraChanged: new Signal(),

    geometryChanged: new Signal(),

    objectSelected: new Signal(),
    objectFocused: new Signal(),

    objectAdded: new Signal(),
    objectChanged: new Signal(),
    objectRemoved: new Signal(),
    objectRemoteSaved: new Signal(),

    // zhouwr add by 2020-11-04
    objectConfiged: new Signal(), //
    objectAllConfiged: new Signal(), //

    helperAdded: new Signal(),
    helperRemoved: new Signal(),

    materialAdded: new Signal(),
    materialChanged: new Signal(),
    materialRemoved: new Signal(),

    scriptAdded: new Signal(),
    scriptChanged: new Signal(),
    scriptRemoved: new Signal(),

    windowResize: new Signal(),

    showGridChanged: new Signal(),
    showHelpersChanged: new Signal(),
    enableTransformControlsChanged: new Signal(),
    refreshSidebarObject3D: new Signal(),
    historyChanged: new Signal(),

    viewportCameraChanged: new Signal(),

    setReaderSized: new Signal(),
    deployConfigLoaded: new Signal(),

  }

  // this.axios = axios.create;
  this.config = new Config()
  this.history = new _History(this)
  this.storage = new _Storage()
  this.strings = new Strings(this.config)

  this.loader = new Loader(this)
  this.loaderRemote = new LoaderRemote(this)

  this.camera = this.DEFAULT_CAMERA.clone()

  this.scene = new THREE.Scene()
  this.scene.name = 'Scene'
  // this.scene.background = new THREE.Color(0xaaaaaa)

  this.sceneHelpers = new THREE.Scene()

  this.object = {}
  this.geometries = {}
  this.materials = {}
  this.textures = {}
  this.scripts = {}

  this.materialsRefCounter = new Map() // tracks how often is a material used by a 3D object

  this.animations = {}
  this.mixer = new THREE.AnimationMixer(this.scene)

  this.selected = null
  this.helpers = {}

  this.storageRemote = new StorageRemote(this)

  this.addCamera(this.camera)
}

Editor.prototype = {
  /**
   * 配置模型对象
   * @author zhouwr
   * @param object 模型对象
   * @param deployConfig 部署配置信息
   * @returns {*}
   */
  configObj: function (object, deployConfig) {
    console.log('配置模型对象：', deployConfig.id)
    object = object.scene || object
    object.uuid = deployConfig.id
    object.name = deployConfig.name
    object.file = deployConfig.modelFile
    object.userData['modelFile'] = deployConfig.modelFile
    object.userData['type'] = deployConfig.type
    object.userData['scheme'] = deployConfig.schemeId

    if (deployConfig.attribute) {
      let position = deployConfig.attribute['position']
      let rotation = deployConfig.attribute['rotation']
      let scale = deployConfig.attribute['scale']

      if (position) object.position.set(position.x || 0, position.y || 0, position.z || 0)
      if (rotation) object.rotation.set(rotation.x || 0, rotation.y || 0, rotation.z || 0)
      if (scale) object.scale.set(scale.x || 1, scale.y || 1, scale.z || 1)
    }

    return object
  },

  /**
   *
   * @param  letiable
   */
  getQueryVariable: function (letiable) {
    let query = window.location.search.substring(1)
    let lets = query.split('&')
    for (let i = 0; i < lets.length; i++) {
      let pair = lets[i].split('=')
      if (pair[0] == letiable) {
        return pair[1]
      }
    }
    return ''
  },

  setScene: function (scene) {
    const scope = this
    console.log('setScene')

    this.scene.uuid = scene.uuid
    this.scene.name = scene.name

    this.scene.background = scene.background !== null ? scene.background.clone() : null

    if (scene.fog !== null) this.scene.fog = scene.fog.clone()

    this.scene.userData = JSON.parse(JSON.stringify(scene.userData))

    // avoid render per object

    this.signals.sceneGraphChanged.active = false

    scene.children
      .filter(obj => obj.userData && obj.userData.modelFile)
      .map(obj => {
        /**
         * zhouwr add by 2020-11-06
         * 给模型对象添加属性file，用于模型加载时与配置信息比对文件
         */
        console.log(obj.userData.modelFile)
        obj['file'] = obj.userData.modelFile
        scope.addObject(obj)
      });
    this.signals.sceneGraphChanged.active = true
    this.signals.sceneGraphChanged.dispatch()
  },

  //

  addObject: function (object, parent, index) {
    var scope = this

    object.traverse(function (child) {
      if (child.geometry !== undefined) scope.addGeometry(child.geometry)
      if (child.material !== undefined) scope.addMaterial(child.material)

      scope.addCamera(child)
      scope.addHelper(child)
    })

    if (parent === undefined) {
      this.scene.add(object)
    } else {
      parent.children.splice(index, 0, object)
      object.parent = parent
    }

    this.config.setKey('autosave', true)
    this.signals.objectAdded.dispatch(object)
    this.signals.sceneGraphChanged.dispatch()
  },

  moveObject: function (object, parent, before) {
    if (parent === undefined) {
      parent = this.scene
    }

    parent.add(object)

    // sort children array

    if (before !== undefined) {
      var index = parent.children.indexOf(before)
      parent.children.splice(index, 0, object)
      parent.children.pop()
    }

    this.signals.sceneGraphChanged.dispatch()
  },

  nameObject: function (object, name) {
    object.name = name
    this.signals.sceneGraphChanged.dispatch()
  },

  removeObject: function (object) {
    if (object.parent === null) return // avoid deleting the camera or scene

    var scope = this

    object.traverse(function (child) {
      scope.removeCamera(child)
      scope.removeHelper(child)

      if (child.material !== undefined) scope.removeMaterial(child.material)
    })

    object.parent.remove(object)

    this.config.setKey('autosave', true)
    this.signals.objectRemoved.dispatch(object)
    this.signals.sceneGraphChanged.dispatch()
  },

  addGeometry: function (geometry) {
    this.geometries[geometry.uuid] = geometry
  },

  setGeometryName: function (geometry, name) {
    geometry.name = name
    this.signals.sceneGraphChanged.dispatch()
  },

  addMaterial: function (material) {
    if (Array.isArray(material)) {
      for (var i = 0, l = material.length; i < l; i++) {
        this.addMaterialToRefCounter(material[i])
      }
    } else {
      this.addMaterialToRefCounter(material)
    }

    this.signals.materialAdded.dispatch()
  },

  addMaterialToRefCounter: function (material) {
    var materialsRefCounter = this.materialsRefCounter

    var count = materialsRefCounter.get(material)

    if (count === undefined) {
      materialsRefCounter.set(material, 1)
      this.materials[material.uuid] = material
    } else {
      count++
      materialsRefCounter.set(material, count)
    }
  },

  removeMaterial: function (material) {
    if (Array.isArray(material)) {
      for (var i = 0, l = material.length; i < l; i++) {
        this.removeMaterialFromRefCounter(material[i])
      }
    } else {
      this.removeMaterialFromRefCounter(material)
    }

    this.signals.materialRemoved.dispatch()
  },

  removeMaterialFromRefCounter: function (material) {
    var materialsRefCounter = this.materialsRefCounter

    var count = materialsRefCounter.get(material)
    count--

    if (count === 0) {
      materialsRefCounter.delete(material)
      delete this.materials[material.uuid]
    } else {
      materialsRefCounter.set(material, count)
    }
  },

  getMaterialById: function (id) {
    var material
    var materials = Object.values(this.materials)

    for (var i = 0; i < materials.length; i++) {
      if (materials[i].id === id) {
        material = materials[i]
        break
      }
    }

    return material
  },

  setMaterialName: function (material, name) {
    material.name = name
    this.signals.sceneGraphChanged.dispatch()
  },

  addTexture: function (texture) {
    this.textures[texture.uuid] = texture
  },

  addAnimation: function (object, animations) {
    if (animations.length > 0) {
      this.animations[object.uuid] = animations
    }
  },

  //

  addCamera: function (camera) {
    if (camera.isCamera) {
      this.signals.cameraAdded.dispatch(camera)
    }
  },

  removeCamera: function (camera) {

    if (this.cameras[camera.uuid] !== undefined) {

      delete this.cameras[camera.uuid];

      this.signals.cameraRemoved.dispatch(camera);

    }

  },

  addHelper: (function () {
    var geometry = new THREE.SphereBufferGeometry(2, 4, 2)
    var material = new THREE.MeshBasicMaterial({
      color: 0xff0000,
      visible: false,
    })

    return function (object, helper) {
      if (helper === undefined) {
        if (object.isCamera) {
          helper = new THREE.CameraHelper(object)
        } else if (object.isPointLight) {
          helper = new THREE.PointLightHelper(object, 1)
        } else if (object.isDirectionalLight) {
          helper = new THREE.DirectionalLightHelper(object, 1)
        } else if (object.isSpotLight) {
          helper = new THREE.SpotLightHelper(object, 1)
        } else if (object.isHemisphereLight) {
          helper = new THREE.HemisphereLightHelper(object, 1)
        } else if (object.isSkinnedMesh) {
          helper = new THREE.SkeletonHelper(object.skeleton.bones[0])
        } else {
          // no helper for this object type
          return
        }

        var picker = new THREE.Mesh(geometry, material)
        picker.name = 'picker'
        picker.userData.object = object
        helper.add(picker)
      }

      this.sceneHelpers.add(helper)
      this.helpers[object.id] = helper

      this.signals.helperAdded.dispatch(helper)
    }
  })(),

  removeHelper: function (object) {
    if (this.helpers[object.id] !== undefined) {
      var helper = this.helpers[object.id]
      helper.parent.remove(helper)

      delete this.helpers[object.id]

      this.signals.helperRemoved.dispatch(helper)
    }
  },

  //

  addScript: function (object, script) {
    if (this.scripts[object.uuid] === undefined) {
      this.scripts[object.uuid] = []
    }

    this.scripts[object.uuid].push(script)

    this.signals.scriptAdded.dispatch(script)
  },

  removeScript: function (object, script) {
    if (this.scripts[object.uuid] === undefined) return

    var index = this.scripts[object.uuid].indexOf(script)

    if (index !== -1) {
      this.scripts[object.uuid].splice(index, 1)
    }

    this.signals.scriptRemoved.dispatch(script)
  },

  getObjectMaterial: function (object, slot) {
    var material = object.material

    if (Array.isArray(material) && slot !== undefined) {
      material = material[slot]
    }

    return material
  },

  setObjectMaterial: function (object, slot, newMaterial) {
    if (Array.isArray(object.material) && slot !== undefined) {
      object.material[slot] = newMaterial
    } else {
      object.material = newMaterial
    }
  },

  /**
   * 获取整个对象
   * @author zhouwr
   * @date 2020-11-07
   * @param object 对象片段
   * @returns whole object 整体对象
   */
  getWholeObject: function (object) {
    let pobj = object.parent
    if (pobj.type === 'Scene') {
      return object
    } else {
      return this.getWholeObject(pobj)
    }
  },

  select: function (object) {
    if (this.selected === object) return

    var uuid = null

    if (object !== null) {
      uuid = object.uuid
    }

    this.selected = object

    this.config.setKey('selected', uuid)
    this.signals.objectSelected.dispatch(object)
  },

  selectById: function (id) {
    if (id === this.camera.id) {
      this.select(this.camera)
      return
    }

    this.select(this.scene.getObjectById(id, true))
  },

  selectByUuid: function (uuid) {
    var scope = this

    this.scene.traverse(function (child) {
      if (child.uuid === uuid) {
        scope.select(child)
      }
    })
  },

  deselect: function () {
    this.select(null)
  },

  focus: function (object) {
    if (object !== undefined) {
      this.signals.objectFocused.dispatch(object)
    }
  },

  focusById: function (id) {
    this.focus(this.scene.getObjectById(id, true))
  },

  clear: function () {
    this.history.clear()
    this.storage.clear()

    this.camera.copy(this.DEFAULT_CAMERA)
    this.scene.name = 'Scene'
    this.scene.userData = {}
    this.scene.background = new THREE.Color(0xaaaaaa)
    this.scene.fog = null

    var objects = this.scene.children

    while (objects.length > 0) {
      this.removeObject(objects[0])
    }

    this.geometries = {}
    this.materials = {}
    this.textures = {}
    this.scripts = {}

    this.materialsRefCounter.clear()

    this.animations = {}
    this.mixer.stopAllAction()

    this.deselect()

  },

  //

  fromJSON: function (json) {
    var scope = this

    var loader = new THREE.ObjectLoader()
    var camera = loader.parse(json.camera)

    this.camera.copy(camera)
    this.camera.aspect = this.DEFAULT_CAMERA.aspect
    this.camera.updateProjectionMatrix()

    this.history.fromJSON(json.history)
    this.scripts = json.scripts

    return new Promise((resolve, reject) => {
      loader.parse(json.scene, function (scene) {
        scope.setScene(scene)
        return resolve(scene)
      })
    })
  },

  toJSON: function () {
    // scripts clean up

    var scene = this.scene
    var scripts = this.scripts

    for (var key in scripts) {
      var script = scripts[key]

      if (script.length === 0 || scene.getObjectByProperty('uuid', key) === undefined) {
        delete scripts[key]
      }
    }

    //

    return {
      metadata: {},
      project: {
        shadows: this.config.getKey('project/renderer/shadows'),
      },
      camera: this.camera.toJSON(),
      scene: this.scene.toJSON(),
      scripts: this.scripts,
      history: this.history.toJSON(),
    }
  },

  objectByUuid: function (uuid) {
    return this.scene.getObjectByProperty('uuid', uuid, true)
  },

  execute: function (cmd, optionalName) {
    this.history.execute(cmd, optionalName)
  },

  undo: function () {
    this.history.undo()
  },

  redo: function () {
    this.history.redo()
  },
}

export {Editor}
